xtask\tasks\fuzz/
html_coverage.rs1use anyhow::Context;
8use std::path::Path;
9use std::path::PathBuf;
10
11pub(super) fn generate_html_coverage_report(
12 ctx: &crate::XtaskCtx,
13 target_fuzz_dir: &Path,
14 target_name: &str,
15) -> Result<(), anyhow::Error> {
16 let coverage_profdata_file = target_fuzz_dir
18 .join("coverage")
19 .join(target_name)
20 .join("coverage.profdata");
21
22 let coverage_bin = {
25 let mut coverage_bin: Option<PathBuf> = None;
26 for e in walkdir::WalkDir::new(ctx.root.join("target")) {
27 let e = e?;
28 if e.file_name() != target_name
29 || !e.path().components().any(|c| c.as_os_str() == "coverage")
30 {
31 continue;
32 }
33
34 if let Some(existing_bin) = &coverage_bin {
38 panic!(
39 "xtask bug: found multiple potential coverage bins: {} and {}",
40 existing_bin.display(),
41 e.path().display()
42 )
43 } else {
44 coverage_bin = Some(e.into_path());
45 }
46 }
47 coverage_bin.expect("xtask bug: failed to find the coverage-instrumented fuzzer bin")
48 };
49
50 let llvm_tools_dir = 'llvm_tools_dir: {
51 let output = std::process::Command::new("rustc")
52 .args(["+nightly", "--print", "sysroot"])
53 .output()
54 .context("failed to run `rustc +nightly --print sysroot`")?;
55 let rustc_sysroot = String::from_utf8_lossy(&output.stdout).to_string();
56 let rustc_sysroot = rustc_sysroot.trim();
57
58 for e in walkdir::WalkDir::new(rustc_sysroot) {
59 let e = e?;
60 if e.file_name() == "llvm-profdata" {
61 let mut path = e.into_path();
62 path.pop();
63 break 'llvm_tools_dir path;
64 }
65 }
66
67 anyhow::bail!(
68 "failed to find `llvm-tools` directory. did you run `rustup +nightly component add llvm-tools`?"
69 )
70 };
71
72 if which::which("lcov").is_err() {
73 anyhow::bail!(
74 "could not find `lcov` on your $PATH! make sure it's installed (e.g: `apt install lcov`)"
75 )
76 }
77
78 let coverage_dir = coverage_bin.parent().unwrap();
79 let coverage_lcov_file = coverage_dir.join("coverage.lcov");
80 {
81 let lcov_output = std::fs::File::create(&coverage_lcov_file)?;
82
83 let mut cmd = std::process::Command::new(llvm_tools_dir.join("llvm-cov"));
84 let mut cmd = cmd
85 .arg("export")
86 .arg("-instr-profile")
87 .arg(coverage_profdata_file)
88 .arg("-format=lcov")
89 .arg("-object")
90 .arg(&coverage_bin)
91 .args(["--ignore-filename-regex", "rustc"])
92 .stdout(std::process::Stdio::from(lcov_output))
93 .spawn()?;
94 if !cmd.wait()?.success() {
95 anyhow::bail!("failed while running `llvm-cov`")
96 }
97 }
98
99 let html_report_dir = coverage_dir.join(format!("lcov_html_{}", target_name));
100 {
101 let mut cmd = std::process::Command::new("lcov");
103 let mut cmd = cmd.arg("--summary").arg(&coverage_lcov_file).spawn()?;
104 if !cmd.wait()?.success() {
105 anyhow::bail!("failed while running `lcov`")
106 };
107
108 if html_report_dir.exists() {
110 fs_err::remove_dir_all(&html_report_dir)?;
111 }
112 fs_err::create_dir(&html_report_dir)?;
113
114 let mut cmd = std::process::Command::new("genhtml");
116 let mut cmd = cmd
117 .arg("-o")
118 .arg(&html_report_dir)
119 .arg("--legend")
120 .arg("--highlight")
121 .arg(coverage_lcov_file)
122 .spawn()?;
123 if !cmd.wait()?.success() {
124 anyhow::bail!("failed while running `genhtml`")
125 }
126 }
127
128 log::info!("");
129 log::info!(
130 "success! html report generated at {}",
131 html_report_dir.join("index.html").display()
132 );
133
134 Ok(())
135}